/**
 * 
 */
package com.celephais.common.client.ui;

import java.util.ArrayList;
import java.util.List;

import com.celephais.common.client.TransferObjectAccessor;
import com.celephais.common.client.model.ReferenceUI;
import com.celephais.common.client.model.TableColumn;
import com.celephais.common.client.model.TransferObject;
import com.celephais.common.client.model.UnimplementedException;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FocusWidget;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.TextBoxBase;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * @author Eugene Shen
 * A dialog box for adding / editing items.  Normally gets shown when
 * the user single-clicks on the add button or double-clicks on a row in
 * the ObjectAccessTable.
 */
public class ObjectEditDialog extends DialogBox implements ModalDialogCallback {
	
	// SETTINGS
	// Update?  Or add?
	private boolean update;
	// The table columns (we CAN get it from service again, but its costly).
	private TableColumn[] columns;
	// The service to call if an add/update is committed.  Can't parameterize for some stupid reason...	
	private TransferObjectAccessor<TransferObject> accessor;
	// The calling table...
	private ObjectAccessTable table;
	// The object being modified (null if not update).
	private TransferObject object;
	
	// COMPONENTS
	private final VerticalPanel outerPanel = new VerticalPanel();
	private final Grid contentPanel = new Grid();
	private final ScrollPanel contentScrollPanel = new ScrollPanel(contentPanel);
	private final HorizontalPanel btnsPanel = new HorizontalPanel();
	private final Button addBtn = new Button("Add");
	private final Button updateBtn = new Button("Update");
	private final Button deleteBtn = new Button("Delete");
	private final Button cancelBtn = new Button("Cancel");
	
	// Mapping columns to input fields
	private final ArrayList<Widget> userInput = new ArrayList<Widget>();
	
	// Reference and Child Dialogs
	private ReferencePickerDialog refPicker;
	private ChildCollectionEditorDialog childEditor;
	
	public ObjectEditDialog() {
		super();
		initComponents();
	}
	
	/**
	 * The method to call when you need to display this dialog.  Do NOT
	 * use show() or center() directly, or the values will not be set
	 * correctly.
	 * @param isUpdate Update?  Or Add?
	 * @param columns The TableColumns describing the fields in this object.
	 * @param service The service to add/update/delete objects.
	 * @param object The actual object being modified (null if add).
	 */
	public void show(boolean isUpdate, TableColumn[] columns,
			TransferObjectAccessor<TransferObject> service, ObjectAccessTable table,
			TransferObject object) {
		this.update = isUpdate;
		this.columns = columns;
		this.accessor = service;
		this.table = table;
		this.object = object;
		
		if (update) {
			setText("Edit Item");
			addBtn.setEnabled(false);
			updateBtn.setEnabled(true);
			deleteBtn.setEnabled(true);
		}
		else {
			setText("Add New Item");
			addBtn.setEnabled(true);
			updateBtn.setEnabled(false);
			deleteBtn.setEnabled(false);
		}
		
		loadObjectValues();
		center();
	}
	
	/**
	 * Creates the input fields and loads the values
	 */
	protected void loadObjectValues() {
		contentPanel.resize(columns.length, 2);
		userInput.clear();
		for (int i=0; i<columns.length; ++i) {
			TableColumn attrib = columns[i];
			Label attribName = new Label(attrib.getTitle());
			contentPanel.setWidget(i, 0, attribName);
			
			String val = (object != null) ? object.getValue(attrib.getName()) : "";
			Widget widget = createWidget(attrib, val);
			userInput.add(widget);
			contentPanel.setWidget(i, 1, widget);
		}
	}
	
	/**
	 * Creates a user-input widget based on the TableColumn information
	 * and the current value.
	 * @param col The metadata describing the field.
	 * @param value The current value of the field.
	 * @return A new widget for the GUI, with values and input
	 *         constraints set.
	 */
	protected Widget createWidget(TableColumn col, String value) {
		Widget rtn = null;
		switch(col.getDisplayType()) {
			case TEXT_BOX:
				rtn = new TextBox();
				// What do unicode characters count as?  1 or 2 chars?
				// String fields can only store < 500 bytes, so we need
				// to be careful here.
				((TextBox)rtn).setMaxLength(249);
				
				// If there are valid values, then wrap the TextBox with
				// a SuggestBox.
				if (col.getValidValues() != null && col.getValidValues().length > 0) {
					MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
					for (String val : col.getValidValues()) {
						if (val != null && !val.isEmpty()) {
							oracle.add(val);
						}
					}
					rtn = new SuggestBox(oracle, (TextBox)rtn);
				}
				break;
			case TEXT_AREA:
				rtn = new TextArea();
				break;
			case CHECK_BOX:
				rtn = new CheckBox();
				((CheckBox)rtn).setValue(Boolean.parseBoolean(value));
				break;
			case LIST_BOX:
				rtn = new ListBox(false); // Single selection only.
				for (int i=0; i<col.getValidValues().length; ++i) {
					String val = col.getValidValues()[i];
					((ListBox)rtn).addItem(val);
					if (val.equalsIgnoreCase(value)) {
						((ListBox)rtn).setSelectedIndex(i);
					}
				}
				break;
			case CHILD:
				rtn = new HorizontalPanel();
				TextBox tb = new TextBox();
				tb.setValue(value);
				tb.setEnabled(false);
				Button editBtn = new Button("Edit");
				editBtn.addClickHandler(new ChildEditHandler(col.getName()));
				((HorizontalPanel)rtn).add(tb);
				((HorizontalPanel)rtn).add(editBtn);
				break;
			case REFERENCE:
				rtn = new HorizontalPanel();
				TextBox refBox = new TextBox();
				refBox.setValue(value);
				refBox.setEnabled(false);
				Button changeBtn = new Button("Change");
				changeBtn.addClickHandler(new ReferenceChangeHandler(col.getName()));
				((HorizontalPanel)rtn).add(refBox);
				((HorizontalPanel)rtn).add(changeBtn);
				break;
			default:
				// TODO: Raise Error?
				break;
		}
		
		// All valid selections so far are FocusWidgets; so this is
		// essentially just a null-check.
		if (rtn instanceof FocusWidget && !col.isShow()) {
			((FocusWidget)rtn).setEnabled(false);
		}
		
		// Covers only TextBox and TextArea.
		if (rtn instanceof TextBoxBase) {
			((TextBoxBase)rtn).setText(value);
		}
		
		return rtn;
	}
	
	protected void setObjectValues() {
		if (userInput.size() != columns.length) {
			// WTF?!
			return;
		}
		for (int i=0; i<columns.length; ++i) {
			TableColumn attrib = columns[i];
			Widget ui = userInput.get(i);
			if (attrib.isShow()) {
				String value = null;
				if (ui instanceof TextBoxBase) {
					value = ((TextBoxBase)ui).getText();
				}
				else if (ui instanceof CheckBox) {
					value = ((CheckBox)ui).getValue().toString();
				}
				else if (ui instanceof ListBox) {
					ListBox lb = ((ListBox)ui);
					int idx = lb.getSelectedIndex();
					if (idx != -1) {
						value = lb.getValue(idx);
					}
				}
				
				if (value != null) {
					object.setValue(attrib.getName(), value);
				}
			}
		}
	}
	
	protected void initComponents() {
		outerPanel.add(contentScrollPanel);
		outerPanel.add(btnsPanel);
		btnsPanel.add(addBtn);
		btnsPanel.add(updateBtn);
		btnsPanel.add(deleteBtn);
		btnsPanel.add(cancelBtn);
		add(outerPanel);
		
		final ServiceCallback addCallback = new ServiceCallback("Add");
		addBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				setObjectValues();
				accessor.add(object, addCallback);
			}
		});
		
		final ServiceCallback updateCallback = new ServiceCallback("Update");
		updateBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				setObjectValues();
				accessor.update(object, updateCallback);
			}
		});
		
		final ServiceCallback deleteCallback = new ServiceCallback("Delete");
		deleteBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				setObjectValues();
				accessor.delete(object, deleteCallback);
			}
		});
		
		cancelBtn.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				ObjectEditDialog.this.hide();
			}
		});
	}
	
	private void showReferencePicker(String colName, ReferenceUI currentPick, List<? extends TransferObject> validRefs) {
		if (refPicker == null) {
			refPicker = new ReferencePickerDialog(this);
		}
		refPicker.setText("Please select the new reference for " + colName);
		refPicker.center(colName, validRefs, currentPick);
	}
	
	private void showChildEditor(String colName) {
		if (childEditor == null) {
			childEditor = new ChildCollectionEditorDialog(this);
		}
		childEditor.setText(object.getName() + " : " + colName);
		childEditor.center(object, colName);
	}
	
	@Override
	public void onDialogCancel(DialogBox caller) {
		// Do nothing for now...
	}

	@Override
	public void onDialogConfirm(DialogBox caller) {
		if (caller == refPicker) {
			try {
				object.setReference(refPicker.getRefColName(), refPicker.getCurrentPick());
				updateColumnDisplay(refPicker.getRefColName(), refPicker.getCurrentPick().getName());
			}
			catch (UnimplementedException e) {
				e.printStackTrace();
			}
		}
		else if (caller == childEditor) {
			updateColumnDisplay(childEditor.getColName(), object.getValue(childEditor.getColName()));
		}
	}
	
	private void updateColumnDisplay(String colName, String value) {
//		System.out.println("Updating " + colName + " with " + value);
		Widget wid = null;
		int colIdx = 0;
		for (; colIdx < columns.length; ++colIdx) {
			TableColumn col = columns[colIdx];
			if (col.getName().equalsIgnoreCase(colName)) {
				wid = createWidget(col, value);
				break;
			}
		}
		if (colIdx < columns.length && wid != null) {
			userInput.remove(colIdx);
			userInput.add(colIdx, wid);
			contentPanel.setWidget(colIdx, 1, wid);
		}
	}
	
	protected class ServiceCallback implements AsyncCallback<Boolean> {
		private String action;
		
		public ServiceCallback(String action) {
			this.action = action;
		}
		
		@Override
		public void onFailure(Throwable caught) {
			// TODO: Do more here.
			String error = action + " failed.  Error: " + caught.getMessage();
			System.err.println(error);
		}

		@Override
		public void onSuccess(Boolean result) {
			if (result) {
				ObjectEditDialog.this.hide();
				if (table != null) {
					table.updateTable(false);
				}
			}
			else {
				// TODO: Do more here.
				String error = "Unable to " + action + " the object; please try again.";
				System.err.println(error);
			}
		}
	}
	
	protected class ReferenceChangeHandler implements ClickHandler {
		private String colName;
		
		public ReferenceChangeHandler(String colName) {
			super();
			this.colName = colName;
		}
		
		@Override
		public void onClick(ClickEvent event) {
//			List<? extends TransferObject> validRefs;
			// TODO: Lock user in "Please Wait" dialog?
			
			try {
				final ReferenceUI ref = object.getReference(colName);
				object.listValidReferences(colName, new AsyncCallback<List<? extends TransferObject>>() {
					@Override
					public void onFailure(Throwable caught) {
						caught.printStackTrace();
						// TODO: Show error?
					}

					@Override
					public void onSuccess(List<? extends TransferObject> result) {
						showReferencePicker(colName, ref, result);
					}
				});
			}
			catch (UnimplementedException e) {
				e.printStackTrace();
				return;
			}
			
//			showReferencePicker(colName, ref, validRefs);
		}
	}
	
	protected class ChildEditHandler implements ClickHandler {
		private String colName;
		
		public ChildEditHandler(String colName) {
			this.colName = colName;
		}
		
		@Override
		public void onClick(ClickEvent event) {
			showChildEditor(colName);
		}
	}

}
